package org.uni_hi.muus.server;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.Vector;

import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;
import javax.microedition.io.file.FileConnection;

import org.uni_hi.muus.replace.MatchMaker;
import org.uni_hi.muus.replace.MuuSReplace;
import org.uni_hi.muus.server.http.Request;
import org.uni_hi.muus.server.http.Response;
import org.uni_hi.muus.server.util.HTTPServerCodes;
import org.uni_hi.muus.server.util.MIMEContentType;
import org.uni_hi.muus.server.util.Query;
import org.uni_hi.muus.util.Configuration;

/*
 * TODO: What if two or more query token from the MuuS-Parameterset (config, start, about), appear at the same time?
 * 	FIX: There's a static order for this: 1) about, 2) start, 3) config  
 */
public class RequestHandler extends Thread{

	private boolean _process=false;

	private Request _request=null;
	private Response _response=null;
	private Query _query=null;
	private Query _postQuery=null;
	private String _btID=null;
	private InputStream _is=null;
	private OutputStream _os=null;
	private DataInputStream _file=null;
	private StreamConnection _c=null;

	public RequestHandler(StreamConnection c) throws IOException {
		_c=c;
		_is=c.openInputStream();
		_os=c.openOutputStream();
		_request=new Request(new DataInputStream(_is), new PrintStream(_os));
		this.initHandler();
		this.start();
		//		handleRequest();
	}

	private void initHandler(){
		_request.readRequestLine();
		this.parseForUUID();
	}

	private void checkRequestForPOSTQuery(){
		if(_request.getMethod().equals(HTTPServerCodes.HTTP_POST)){
			try {
				if(_request.get(Request.FIELD_CONTENT_TYPE).equals(MIMEContentType.TYPE_APPLICATION_WWW_FORM_DATA)){
					_postQuery=new Query(new String(_request.getContent()));
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * Checks if there's an empty path
	 * @return true if path only consists of /
	 */
	protected boolean isPathEmpty(){
		return "/".equals(_request.getURL().getPath());
	}
	
	/**
	 * Checks if the request path contains only one '/'
	 * @return true if only one '/' in the Request path
	 */
	protected boolean isResourceUnderRoot(){
		return _request.getURL().getPath().lastIndexOf('/')==0;
	}

	protected void parseForUUID(){
		if(!isPathEmpty()){
			String fp=_request.getURL().getFirstPathEntry();
			if(fp.length()==12){
				_btID = fp;
			}
		}
	}

	
	
	protected boolean isLocalUUID(){
		if(_btID==null)return false;
		return (_btID.equalsIgnoreCase(Configuration.getLocalBTID()));
	}

	protected boolean isRemoteUUID(){
		return (_btID!=null&&!isLocalUUID());
	}

	/**
	 * Checks whether the request is for 
	 *  - a local resource (LocalDevice UUID)
	 *  - a remote resource (RemoteDevice UUID) or
	 *  - a special resource
	 */
	public void handleRequest(){
		try {
			if(isRemoteUUID()){//a remote device, try and connect via bluetooth!
				handleRemoteRequest();
			}else{
				_request.readRequest();// as we read only the request, now it's time to read the rest
				_response=new Response(_request);
				_query=_request.getURL().getQuery();
				checkRequestForPOSTQuery();

				if(_response.getStatusCode()!=-1){//this means we already got an error, no need to go further
					handleErrorInRequest();
				}else if(isPathEmpty()){ //this means we've got a request for a special resource
					handleSpecialRequest();
				}else if(isLocalUUID()){//the local resource, means we've gotta search our filesystem
					if(_request.getURL().getLocalPath().equals("/")){
						processSpecialResource(HTTPServerCodes.MuuS_ABOUT_FILE);
					}else{
						handleLocalRequest();
					}
				}else{//try and load data from the jar file
					processSpecialResource("/web"+_request.getURL().getPath());
					//handleUnknowRequest();
				}
				returnResponse();
			}
		} catch (Exception e) {
			_response.setStatusCode(500, "Unknown Internal Server error!");
			e.printStackTrace();
		}finally{
			closeStream(_file);
			closeStream(_is);
			closeStream(_os);
		}
		System.out.println("Done handling request!");
	}

	private void returnFile() {
		if(_file!=null){//a file was found an needs to be returned
			System.out.println("Writing file to stream");
			if(_process){
				processOutputStream();
			}else{
				returnOutputStream();
			}
		}		
	}

	private void returnOutputStream(){
		try {
			int read=0;
			byte[] buff=new byte[256];
			while((read=_file.read(buff))!=-1){
				_os.write(buff,0,read);
			}
			_os.flush();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private void processOutputStream(){
		int b=0;
		boolean check=false, replace=false;
		StringBuffer temp=new StringBuffer();
		String command=null;
		Vector v=new Vector(5);
		try {
			while((b=_file.read())!=-1){
				if(replace){
					//this indicates a new parameter
					if(b==' '){
						if(command==null){
							command=temp.toString();
						}else{
							v.addElement(temp.toString());
						}						
						temp=new StringBuffer();
						//here we check if the end of the replace parameter is reached...
					}else if(b=='%'){
						check=false;
					}else if(b=='>'&&!check){//we've reached the end of the parameter
						replace=false;
						if(command==null){
							command=temp.toString();
						}else{
							v.addElement(temp.toString());
						}
						temp=new StringBuffer();
						MuuSReplace repl=(MuuSReplace)MatchMaker.getReplaceCommander(command);
						_os.write(repl.getReplacement(v,_postQuery));
						continue;
					}else if(!check){
						check=true;
					}else{
						temp=temp.append((char)b);
					}
				}else if(b=='<'){//here we start to check if the replace parameter starts
					check=true;
				}else if(b=='%'&&check){
					replace=true;
				}else if(check){//if check was found last time but no replace begin [%]
					check=false;
					_os.write('<');
				}
				/* if we're not in replace mode or our indicator guess it could begin
				 * we return the read byte
				 */
				if(!check&&!replace){
					_os.write(b);
				}
			}
			_os.flush();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	private void returnResponse(){
		_response.returnResponse();
		returnFile();
	}

	private void handleUnknowRequest() {
		System.out.println("Handling Unknown Request! "+_request.getRequestLine());
		_response.setStatusCode(404, "The requested resource was neither special nor local or remote - better luck next time!");
	}

	/**
	 * Processes a request for a local resource and prepares a DataInputStream to
	 * be returned to the sender.
	 */
	private void handleLocalRequest() {
		System.out.println("Handling Local Request!");
		String localPath=Configuration.getRootPath();
		localPath+=_request.getURL().getLocalPath();
		System.out.println(localPath);
		try {
			FileConnection f=(FileConnection)Connector.open(localPath);
			if(f.exists()){//if the requested path exists
				if(f.isDirectory()){
					_response.setStatusCode(403, "Directory access not allowed.");
				}else{//and is not a directory
					//we can now read some file data and prepare the response
					String ext=_request.getURL().getFileExtension();
					if(ext.equalsIgnoreCase("muus"))_process=true;
					ext=MIMEContentType.getContentType(ext);
					_response.addValuePair(Response.FIELD_CONTENT_LENGTH, Long.toString(f.fileSize()));
					_response.addValuePair(Response.FIELD_CONTENT_TYPE, ext);
					_response.addValuePair(Response.FIELD_LAST_MODIFIED, Long.toString(f.lastModified()));
					_file=f.openDataInputStream();//if everythings ok we can transfer the file to our field 
				}
			}else{
				_response.setStatusCode(404, "File just not found.");
			}
		} catch (Exception e) {
			_response.setStatusCode(404, "Error while opening the requested file.");
			e.printStackTrace();
		}
	}

	private void handleSpecialRequest() {
		_process=true;
		System.out.println("Handling special request!");
		String resource=null;
		//no query or "?start" -> Startpage containing Links to available devices
		if(_query==null){
			resource=HTTPServerCodes.MuuS_START_FILE;
		}else if (_query.containsKey(HTTPServerCodes.MuuS_START_PARAMETER)) {
			resource=HTTPServerCodes.MuuS_START_FILE;
			//"?config" -> Page for configuration the local MuuServer
		}else if(_query.containsKey(HTTPServerCodes.MuuS_CONFIG_PARAMETER)){
			resource=HTTPServerCodes.MuuS_CONFIG_FILE;
			//"?about" -> Page for information about the current device [also available via "http://localhost/<myBTID>/"
		}else if(_query.containsKey(HTTPServerCodes.MuuS_ABOUT_PARAMETER)){
			if(_query.get(HTTPServerCodes.MuuS_ABOUT_PARAMETER).equals(HTTPServerCodes.MuuS_EDIT_VALUE)){
				resource=HTTPServerCodes.MuuS_ABOUT_EDIT_FILE;
			}else{
				resource=HTTPServerCodes.MuuS_ABOUT_FILE;
			}
		}
		System.out.println("Resource: "+resource);
		processSpecialResource(resource);
	}

	private void processSpecialResource(String resource){
		if(resource!=null){
			InputStream tis=this.getClass().getResourceAsStream(resource);
			if(tis!=null){
				_file=new DataInputStream(tis);
				String ext=resource.substring(resource.lastIndexOf('.')+1);
				ext=MIMEContentType.getContentType(ext);
				_response.addValuePair(Response.FIELD_CONTENT_TYPE, ext);
			}else{
				_response.setStatusCode(404, "Special resource could not be found");
			}
		}
	}

	private void handleErrorInRequest() {
		System.out.println("Handling error in request!"+_request.getStatusCode()+_request.getStatusExplanation());

	}

	private void handleRemoteRequest() {
		System.out.println("Handling remote Request!");
		String url="btspp://"+_btID+":1;master=false;encrypt=false;authenticate=false";
		StreamConnection sc=null;
		DataOutputStream dos=null;
		DataInputStream dis=null;
		System.out.println("Opening to url: "+url);
		try{
			sc=(StreamConnection)Connector.open(url,Connector.READ_WRITE,true);
			dos=sc.openDataOutputStream();
			dos.write(_request.getRequestLine().getBytes());
			dis=new DataInputStream(_is);
			int readBytes=0,size=dis.available();
			byte[] buffer=new byte[256];
			while(size>0){
				readBytes=dis.read(buffer);
				dos.write(buffer, 0, readBytes);
				size-=readBytes;
			}
			dos.flush();
			System.out.println("Request Data written!");

			//Now we turn the game around and read the returning data
			dos.close();
			dis.close();
			dos=new DataOutputStream(_os);
			dis=sc.openDataInputStream();
			readBytes=0;
			System.out.println("Starting transfer.");

			while((readBytes=dis.read(buffer))!=-1){
				dos.write(buffer,0,readBytes);
			}
			dos.flush();
			System.out.println("Response Data returned");
		}catch(Exception e){
			_response.setStatusCode(HTTPServerCodes.HTTP_CODE_504, "Unable to connect to remote MuuServer via Bluetooth. Adress was: "+url);
			e.printStackTrace();
		}finally{
			closeStream(dos);
			closeStream(dis);
			closeConnection(sc);
		}
	}

	public void run(){
		try{
			handleRequest();
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			closeStream(_is);
			closeStream(_os);
			closeConnection(_c);
		}
		System.out.println("Thread done!");
	}

	protected void closeConnection(StreamConnection c){
		if(c!=null){
			try {
				c.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	protected void closeStream(OutputStream c){
		if(c!=null){
			try {
				c.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	protected void closeStream(InputStream c){
		if(c!=null){
			try {
				c.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}
